//
// Copyright 2022 The Sparrow Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "sparrow/impl/file.h"
#include <stdio.h>

#ifdef _WIN32
#include <Windows.h>
#include <processthreadsapi.h>
#include <sys/types.h>
#include <sys/timeb.h>
#else
#include <unistd.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <fcntl.h>
#include <errno.h>
#endif

#ifdef _WIN32
sp_handle_t spf_open(const char *path, int op, int share_read) {
    DWORD dwCreationDisposition = 0;
    DWORD dwDesiredAccess = 0;

    if (op & FS_READ_ONLY) {
        dwDesiredAccess = GENERIC_READ;
    } else if (op & FS_WRITE_ONLY) {
        dwDesiredAccess = GENERIC_WRITE;
    } else if (op & FS_READ_WRITE) {
        dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
    }

    if (op & FS_CREATE) {
        dwCreationDisposition = OPEN_ALWAYS;
    } else if (op & FS_TRUNCATE) {
        dwCreationDisposition = TRUNCATE_EXISTING;
    } else {
        dwCreationDisposition = OPEN_EXISTING;
    }
    return CreateFileA(path, dwDesiredAccess, share_read ? FILE_SHARE_READ : 0, NULL,
                       dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
}

int spf_read(sp_handle_t fd, void *buff, int size) {
    DWORD numberOfBytesRead = 0;
    if (!ReadFile(fd, buff, size, &numberOfBytesRead, NULL) && numberOfBytesRead == 0) {
        return -1;
    }
    return (int)(numberOfBytesRead);
}

int spf_read_offset(sp_handle_t fd, void *buff, int size, int64_t offset) {
    DWORD error, numberOfBytesRead = 0;
    OVERLAPPED overlapped = {0};
    overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
    overlapped.Offset = static_cast<DWORD>(offset);

    if (!ReadFile(fd, buff, size, &numberOfBytesRead, &overlapped)) {
        error = GetLastError();
        if (error != ERROR_HANDLE_EOF) {
            return -1;
        }
    }
    return (int)(numberOfBytesRead);
}

int spf_write_offset(sp_handle_t fd, const void *buff, int size, int64_t offset) {
    DWORD NumberOfBytesWritten = 0;
    OVERLAPPED overlapped = {0};
    overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
    overlapped.Offset = static_cast<DWORD>(offset);
    if (!WriteFile(fd, buff, size, &NumberOfBytesWritten, &overlapped)) {
        return -1;
    }
    return (int)(NumberOfBytesWritten);
}
int spf_write(sp_handle_t fd, const void *buff, int size) {
    DWORD NumberOfBytesWritten = 0;
    if (!WriteFile(fd, buff, size, &NumberOfBytesWritten, NULL) && NumberOfBytesWritten == 0) {
        return -1;
    }
    return (int)(NumberOfBytesWritten);
}
int spf_close(sp_handle_t fd) {
    if (CloseHandle(fd)) {
        return 0;
    }
    return -1;
}

int64_t spf_offset(sp_handle_t fd) {
    LARGE_INTEGER distanceToMove = {0};
    LARGE_INTEGER newFilePointer = {0};
    if (SetFilePointerEx(fd, distanceToMove, &newFilePointer, FILE_END)) {
        return (int64_t)(newFilePointer.QuadPart);
    }
    return -1;
}

int spf_set_offset(sp_handle_t fd, int64_t offset, enum sp_where_t wh) {
    LARGE_INTEGER distanceToMove;
    DWORD dwMoveMethod = 0;

    switch (wh) {
    case FSW_BEGIN:
        dwMoveMethod = FILE_BEGIN;
        break;
    case FSW_CURRENT:
        dwMoveMethod = FILE_CURRENT;
        break;
    default:
        dwMoveMethod = FILE_END;
        break;
    }
    distanceToMove.QuadPart = offset;
    if (SetFilePointerEx(fd, distanceToMove, NULL, dwMoveMethod)) {
        return 0;
    }
    return -1;
}

int spf_reserve(sp_handle_t fd, int64_t offset, int64_t length) {
    LARGE_INTEGER distance;
    distance.QuadPart = offset + length;
    if (SetFilePointerEx(fd, distance, NULL, FILE_BEGIN) != FALSE && SetEndOfFile(fd) != FALSE) {
        return 0;
    }
    return -1;
}

int64_t spf_size(sp_handle_t fd) {
    LARGE_INTEGER file_size = {0};
    if (GetFileSizeEx(fd, &file_size)) {
        return (int64_t)(file_size.QuadPart);
    }
    return -1;
}

int spf_truncate(sp_handle_t fd, int64_t size) { return spf_reserve(fd, 0, size); }

int64_t sp_file_size(const char *p) {
    WIN32_FILE_ATTRIBUTE_DATA fad;
    if (!GetFileAttributesExA(p, GetFileExInfoStandard, &fad)) {
        return (int64_t)(-1);
    }
    if ((fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
        return (int64_t)(-1);
    }
    return ((int64_t)(fad.nFileSizeHigh) << (sizeof(fad.nFileSizeLow) * 8)) + fad.nFileSizeLow;
}

int sp_file_delete(const char *p) {
    if (!DeleteFileA(p)) {
        return -1;
    }
    return 0;
}

int sp_file_exists(const char *p) {
    if (GetFileAttributesA(p) != INVALID_FILE_ATTRIBUTES) {
        return 1;
    }
    return 0;
}

int sp_file_rename(const char *oldpath, const char *newpath) {
    if (MoveFileA(oldpath, newpath)) {
        return 0;
    }

    if (ReplaceFileA(newpath, oldpath, NULL, REPLACEFILE_IGNORE_MERGE_ERRORS, NULL, NULL)) {
        return 0;
    }
    return -1;
}

int sp_mk_dir(const char *dir) {
    if (!CreateDirectoryA(dir, NULL)) {
        return -1;
    }
    return 0;
}
int sp_rm_dir(const char *dir) {
    if (!RemoveDirectoryA(dir)) {
        return -1;
    }
    return 0;
}
#else
sp_handle_t spf_open(const char *path, int op, int share_read) {
    int flags = 0;
    int prms = 0;

    if (op & FS_READ_ONLY) {
        flags |= O_RDONLY;
    } else if (op & FS_WRITE_ONLY) {
        flags |= O_WRONLY;
    } else if (op & FS_READ_WRITE) {
        flags |= O_RDWR;
    }

    if (op & FS_CREATE) {
        flags |= O_CREAT;
    } else if (op & FS_TRUNCATE) {
        flags |= O_TRUNC;
    }

    if (share_read) {
        prms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    } else {
        prms = S_IRUSR | S_IWUSR;
    }
    return open(path, flags, prms);
}

int spf_read_offset(sp_handle_t fd, void *buff, int size, int64_t offset) {
    return (int)pread(fd, buff, size, offset);
}
int spf_read(sp_handle_t fd, void *buff, int size) { return (int)read(fd, buff, size); }
int spf_write_offset(sp_handle_t fd, const void *buff, int size, int64_t offset) {
    return (int)pwrite(fd, buff, size, offset);
}
int spf_write(sp_handle_t fd, const void *buff, int size) { return (int)write(fd, buff, size); }
int spf_close(sp_handle_t fd) { return close(fd); }
int64_t spf_offset(sp_handle_t fd) { return (int64_t)lseek(fd, 0, SEEK_CUR); }

int spf_set_offset(sp_handle_t fd, int64_t offset, enum sp_where_t wh) {
    int move_method = 0;
    switch (wh) {
    case FSW_BEGIN:
        move_method = SEEK_SET;
        break;
    case FSW_CURRENT:
        move_method = SEEK_CUR;
        break;
    default:
        move_method = SEEK_END;
        break;
    }
    return lseek(fd, offset, move_method);
}

int spf_reserve(sp_handle_t fd, int64_t offset, int64_t length) {
    return posix_fallocate(fd, offset, length);
}

int64_t spf_size(sp_handle_t fd) {
    int64_t size, backup = spf_offset(fd);
    if (backup == -1 || !spf_set_offset(fd, 0, FSW_END)) {
        return -1;
    }
    size = spf_offset(fd);
    spf_set_offset(fd, backup, FSW_BEGIN);
    return size;
}

int spf_truncate(sp_handle_t fd, int64_t size) { return ftruncate(fd, size); }

int64_t spf_size(const char *p) {
    struct stat path_stat;
    if (stat(p, &path_stat) != 0) {
        return -1;
    }
    if (!S_ISREG(path_stat.st_mode)) {
        return -1;
    }
    return (int64_t)(path_stat.st_size);
}
int sp_file_delete(const char *p) { return unlink(p); }
int sp_file_exists(const char *p) {
    if (access(p, F_OK) == 0) {
        return 1;
    }
    return 0;
}
int sp_file_rename(const char *oldpath, const char *newpath) { return rename(oldpath, newpath); }
int sp_mk_dir(const char *dir) { return mkdir(dir, 0755); }
int sp_rm_dir(const char *dir) { return rmdir(dir); }
#endif
